﻿using System.Collections.Generic;
using Godot.core;
using Godot.core.io;
using Godot.core.os;
using Godot.scene.main;
using Godot.scene.resource;
using Godot.core.math;

namespace Godot.main
{
    public class Main
    { 
	    protected static ulong _last_ticks;
	    protected static ulong _target_ticks;
	    protected static float _time_accum;
	    protected static uint _frames;
	    protected static uint _frame;
	    protected static bool _force_redraw_requested;

        protected static int _video_driver_idx = 0;
        protected static int _audio_driver_idx = 0;
        protected static PathRemap _path_remap = null;
        protected static Globals _globals = null;
        protected static InputMap _input_map = null;
        protected static OS.VideoMode _video_mode = null;
        protected static TranslationServer _translation_server = null;
        protected static PackedData _packed_data = null;
        protected static string _locale = "";

        private static bool __start_success = false;

        protected static void _print_help(string p_binary)
        {
        }

        public static Error setup(string execpath, string[] argv,bool p_second_phase = true)
        {
            RID_OwnerBase.init_rid();

            OS.singleton.initialize_core();
            ObjectTypeDB.init();

            Godot.core.Register.register_core_types();

            Thread._main_thread_id = Thread.caller_ID;

            _globals = Misc.memnew(new Globals());
            _input_map = Misc.memnew(new InputMap());
            _path_remap = Misc.memnew(new PathRemap());
            _translation_server = Misc.memnew(new TranslationServer());
            _video_mode = OS.singleton.get_default_video_mode();
            
            string video_driver = "";
            string audio_driver = "";
            string game_path = ".";
            bool use_custom_res = true;
            bool force_res = false;
            int rtm = 1;

            if(PackedData.singleton == null)
                _packed_data = Misc.memnew(new PackedData());

            List<string> main_args = new List<string>();

            if (_globals.setup(game_path) != Error.OK)
            {
                goto error;
            }
            
            OS.singleton.set_cmdline(execpath, main_args);

            _input_map.load_from_globals();

            if(video_driver == "")
                video_driver = Globals.global_def("display/driver", new Variant(OS.singleton.get_video_driver_name(0)));

            if (!force_res && use_custom_res && _globals.has("display/width"))
                _video_mode.width = _globals.get("display/width");
           
            if (!force_res && use_custom_res && _globals.has("display/height"))
                _video_mode.height = _globals.get("display/height");
            
            if (use_custom_res && _globals.has("display/fullscreen"))
                _video_mode.fullscreen = _globals.get("display/fullscreen");
            
            if (use_custom_res && _globals.has("display/resizable"))
                _video_mode.resizable = _globals.get("display/resizable");

            Globals.global_def("display/width", _video_mode.width);
            Globals.global_def("display/height", _video_mode.height);
            Globals.global_def("display/fullscreen", _video_mode.fullscreen);
            Globals.global_def("display/resizable", _video_mode.resizable);

            if (rtm == -1)
                Globals.global_def("render/thread_model", (int)OS.RenderThreadMode.RENDER_THREAD_SAFE);

            if (rtm >= 0 && rtm < 3)
		        OS.singleton._render_thread_mode = (OS.RenderThreadMode) rtm ;

            if (audio_driver == "") // specified in engine.cfg
                audio_driver = Globals.global_def("audio/driver", OS.singleton.get_audio_driver_name(0));

            for (int i = 0; i <OS.singleton.get_video_driver_count(); i++) 
            {
		        if (video_driver == OS.singleton.get_video_driver_name(i))
                {	
			        __video_driver_idx = i;
			        break;
		        }
	        }

            if (__video_driver_idx < 0)
            {
                OS.singleton.alert("Invalid Video Driver : " + video_driver);
                __video_driver_idx = 0;
            }

            for (int i = 0; i < OS.singleton.get_audio_driver_count(); i++) 
            {	
		        if (audio_driver == OS.singleton.get_audio_driver_name(i)) 
                {	
			        __audio_driver_idx = i;
			        break;
		        }
	        }

	        if (__audio_driver_idx < 0) 
            {	
		        OS.singleton.alert("Invalid Audio Driver: "+ audio_driver );
		        //goto error;
	        }

            {
                string orientation = Globals.global_def("display/orientation", "landscape");

		        if (orientation == "portrait")
			        OS.singleton.set_screen_orientation(OS.ScreenOrientation.SCREEN_PORTRAIT);
		        else if (orientation == "reverse_landscape")
			        OS.singleton.set_screen_orientation(OS.ScreenOrientation.SCREEN_REVERSE_LANDSCAPE);
		        else if (orientation == "reverse_portrait")
			        OS.singleton.set_screen_orientation(OS.ScreenOrientation.SCREEN_REVERSE_PORTRAIT);
		        else if (orientation == "sensor_landscape")
			        OS.singleton.set_screen_orientation(OS.ScreenOrientation.SCREEN_SENSOR_LANDSCAPE);
		        else if (orientation == "sensor_portrait")
			        OS.singleton.set_screen_orientation(OS.ScreenOrientation.SCREEN_SENSOR_PORTRAIT);
		        else if (orientation == "sensor")
			        OS.singleton.set_screen_orientation(OS.ScreenOrientation.SCREEN_SENSOR);
		        else
			        OS.singleton.set_screen_orientation(OS.ScreenOrientation.SCREEN_LANDSCAPE);
	        }

	        OS.singleton.set_iterations_per_second(Globals.global_def("physics/fixed_fps", 60));
	        OS.singleton.set_target_fps(Globals.global_def("application/target_fps", 0));

	        //if (!OS.singleton()->_verbose_stdout) //overrided
		    //    OS::get_singleton()->_verbose_stdout=GLOBAL_DEF("debug/verbose_stdout",false);

	        __message_queue = Misc.memnew(new MessageQueue());

	        Globals.singleton.register_global_defaults();

            if (p_second_phase)
                Main.setup2();

            return Error.OK;

            /*label*/error:

            video_driver = "";
            game_path = "";

            return Error.ERR_INVALID_PARAMETER;
        }

        public static Error setup2()
        {
            OS.singleton.initialize(_video_mode, _video_driver_idx, _audio_driver_idx);

            Register.register_core_singletons();
            
            bool show_logo = true;

            if (show_logo)
            {
            }
  
            Globals.global_def("application/icon", "");

            Globals.singleton.set_custom_property_info("application/icon", new PropertyInfo(Variant.Type.STRING, "application/icon", PropertyHint.PROPERTY_HINT_FILE, "*.png,*.webp"));

            _path_remap.load_remaps();


            Register.register_scene_types();
            Register.register_server_types();

            Register.register_module_types();
            Register.register_driver_types();

            _translation_server.setup();
            if (_locale != "")
            {
                _translation_server.set_locale(_locale);
            }
            _translation_server.load_translations();

            __start_success = true;
            
            _locale = "";
            
            return Error.OK;
        }

        public static bool start()
        {
            if (!__start_success) return false;

            bool editor = false;
            string doc_tool = "";
            bool doc_base = true;
            string game_path = "";
            string script = "";
            string test = "";
            string screen = "";
            string optimize = "";
            string optimize_preset = "";
            string _export_platform = "";
            string _import = "";
            string _import_script = "";
            string dumpstrings = "";
            bool noquit = false;
            bool convert_old = false;
            bool export_debug = false;

            List<string> args = OS.singleton.get_cmdline_args();
            for (int i = 0; i < args.Count; i++)
            {
                /* Will be implemented if needed */
            }

            string main_loop_type = "";

            if (script == "" && game_path == "" && !editor && ((string)Globals.global_def("application/main_scene", "")) != "")
            {
                game_path = Globals.global_def("application/main_scene", "");
            }

            MainLoop main_loop = null;

            if (test != "")
            {
                //Do nothing 'cuz this has only realease mode 
            }
            else if (script != "")
            {
                Script script_res = (Script)ResourceLoader.load(script);
                if (script_res.can_instance())
                {
                    StringName instance_type = script_res.get_instance_base_type();
			        Object obj = ObjectTypeDB.instance(instance_type);
			        MainLoop script_loop = (obj != null) ? (obj as MainLoop) : null; 
                    if (script_loop == null) 
                    {
                        return false;
			        }
			        script_loop.set_init_script(script_res);
			        main_loop = script_loop;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                main_loop_type = Globals.global_def("application/main_loop_type", "");
            }

            if (main_loop == null && main_loop_type == "")
                main_loop_type = "SceneMainLoop";

            if (main_loop == null) 
            {
		        if (!ObjectTypeDB.type_exists(main_loop_type)) 
                {
			        OS.singleton.alert("godot: error: MainLoop type doesn't exist: "+ main_loop_type);
			        return false;
		        } 
                else 
                {
			        Object ml = ObjectTypeDB.instance(main_loop_type);
			        if (ml == null) 
                    {
				        //ERR_EXPLAIN("Can't instance MainLoop type");
				        return false;
			        }

			        main_loop = ml as MainLoop;
			        if (main_loop == null) 
                    {	
				        ml = null;
				        //ERR_EXPLAIN("Invalid MainLoop type");
				        return false;
				
			        }
		        }
	        }

            if (main_loop.is_type("SceneMainLoop"))
            {
                SceneMainLoop sml = main_loop as SceneMainLoop;

                if (!editor)
                {
                    //standard helpers that can be changed from main config

                    if ((bool)Globals.global_def("display/stretch_2d", false))
                    {
                        sml.get_root().set_size_override(true, new Vector2(Globals.singleton.get("display/width"), Globals.singleton.get("display/height")));
                        sml.get_root().set_size_override_stretch(true);
                    }

                    sml.set_auto_accept_quit(Globals.global_def("application/auto_accept_quit", true));
                    string appname = Globals.singleton.get("application/name");
                    appname = TranslationServer.singleton.translate(appname);
                    OS.singleton.set_window_title(appname);


                }
                else
                {
                    Globals.global_def("display/stretch_2d", false);
                    sml.set_auto_accept_quit(Globals.global_def("application/auto_accept_quit", true));
                }

                if (game_path != "")
                {
                    string local_game_path = game_path.Replace("\\", "/");

                    if (!local_game_path.StartsWith("res://"))
                    {
                        bool absolute = (local_game_path.Length > 1) &&
                            (local_game_path[0] == '/' || local_game_path[1] == ':');

                        if (!absolute)
                        {
                            if (Globals.singleton.is_using_datapack())
                            {
                                local_game_path = "res://" + local_game_path;
                            }
                            else
                            {
                                int sep = local_game_path.LastIndexOf("/");

                                if (sep == -1)
                                {
                                    DirAccess da = DirAccess.create(DirAccess.AccessType.ACCESS_FILESYSTEM);
                                    local_game_path = da.get_current_dir() + "/" + local_game_path;
                                    da = null; ;
                                }
                                else
                                {
                                    Error err = Error.FAILED;
                                    DirAccess da = DirAccess.open(local_game_path.Substring(0, sep), ref err);
                                    if (da != null)
                                    {
                                        local_game_path = da.get_current_dir() + "/" + local_game_path.Substring(sep + 1, local_game_path.Length);
                                        da = null;
                                    }
                                }
                            }
                        }
                    }

                    {
					    //autoload
					    List<PropertyInfo> props = new List<PropertyInfo>();
					    Globals.singleton.get_property_list(ref props);
					    foreach(PropertyInfo E in props) 
                        {
						    string s = E.name;
						    if (!s.StartsWith("autoload/"))
							    continue;
						    string name = s.get_slice("/", 1);
						    string path = Globals.singleton.get(s);
						    Resource res = ResourceLoader.load(path);
						    //ERR_EXPLAIN("Can't autoload: "+path);
						    if(res == null) continue;
						    Node n = null;
						    if (res.is_type("PackedScene")) 
                            {
							    PackedScene ps = res as PackedScene;
							    n = ps.instance();
						    } 
                            else if (res.is_type("Script")) 
                            {
							    Script scr = res as Script;
							    StringName ibt = scr.get_instance_base_type();
							    //ERR_EXPLAIN("Script does not inherit a Node: "+path);
							    if( !ObjectTypeDB.is_type(ibt, "Node")) continue;

							    Object obj = ObjectTypeDB.instance(ibt);

							    //ERR_EXPLAIN("Cannot instance node for autoload type: "+String(ibt));
							    if( obj == null ) continue;

							    n = obj as Node;
							    n.set_script(scr);
						    }

						    //ERR_EXPLAIN("Path in autoload not a node or script: "+path);
						    if(n == null) continue;
						    n.set_name(name);
						    sml.get_root().add_child(n);
					    }

				    }

                    local_game_path = Globals.singleton.localize_path(local_game_path);
                }
            }

            OS.singleton.set_main_loop( main_loop );

            return true;
        }

        public static bool iteration()
        {
            ulong ticks = OS.singleton.get_ticks_usec();
	        ulong ticks_elapsed = ticks  - __last_ticks;

	        __frame += ticks_elapsed;

	        __last_ticks = ticks;

	        double step = (double)ticks_elapsed / 1000000.0;

	        float frame_slice = 1.0f / OS.singleton.get_iterations_per_second();

	        if (step > frame_slice * 8)
		        step = frame_slice *8;

	        __time_accum += step;

	        bool exit = false;

            int iters = 0;

	        while(__time_accum > frame_slice) 
            {

		        ulong fixed_begin = OS.singleton.get_ticks_usec();

		        //PhysicsServer::get_singleton()->sync();
		        //PhysicsServer::get_singleton()->flush_queries();

		        //Physics2DServer::get_singleton()->sync();
		        //Physics2DServer::get_singleton()->flush_queries();

		        if (OS.singleton.get_main_loop().iteration( frame_slice )) 
                {
			        exit = true;
			        break;
		        }

		        __message_queue.flush();

		        //PhysicsServer::get_singleton()->step(frame_slice);
		        //Physics2DServer::get_singleton()->step(frame_slice);

		        __time_accum -= frame_slice;
		        __message_queue.flush();
		        //if (AudioServer::get_singleton())
		        //	AudioServer::get_singleton()->update();

		        __fixed_process_max = System.Math.Max(OS.singleton.get_ticks_usec() - fixed_begin, __fixed_process_max);
		        iters++;
	        }
            return true;
        }

        private static int __video_driver_idx = -1;
        private static int __audio_driver_idx = -1;
        private static ulong __last_ticks;
        private static double __time_accum;
        private static ulong __frames;
        private static ulong __frame;
        private static ulong __fixed_process_max;
        private static MessageQueue __message_queue;
    }
}
